/*
 Copyright 2008 Jason E. Smith Licensed under the Apache License, Version 2.0 (the "License");
 You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 */

/*
 * Ported from Thomas Robinsons, JSON Diff tool located at:
 * http://tlrobinson.net/projects/js/jsondiff/
 * This one is slightly more reusable and will be part of Sunrays core ui toolkit.
 * 
 * USAGE:
 * A. there are 2 approaches. First, from a Model.
 * ---------------------------------------------------
	ensure("JsonDiff/*");
	
	var o = new js.Model({
	    before: {person:{name:"jason"}},
	    after : {person:{name:"jay"}}
	});
	application.appendChild(
		o.asComponentOn(controls.JsonDiff)
	)
	
	
	B. or, from the View itself:
	---------------------------------------------------
	ensure("JsonDiff/*");
	
	application.appendChild(
		controls.JsonDiff({
			before: {person:{name:"jason"}},
	    	after : {person:{name:"jay"}}
		})
	)
	
	
	NOTES:
	The before: and after: params are references to an object that will be compared to each other.
 */
JsonDiff = namespace("controls::JsonDiff", {
    "@inherits": controls.HtmlElement,
    stylesheets : "JsonDiff.css",
    initialize : function(){
        this.render();
        this.makeDraggable();
    },
    
    click : function(e) {
		if(e.target.nodeName.toUpperCase() == "UL")
			if (e.target.hasClass("closed"))
				e.target.removeClass("closed");
			else e.target.addClass("closed");
	},
    element: '<div class="contentbox">\</div>',
	render : function(){
		var el = this.element;
		this.startCompare();
		return el;
	},
   	startCompare : function () {
		var objA = this.get("before");
		var objB = this.get("after");
		
		while (this.element.firstChild)
			this.element.removeChild(this.element.firstChild);
		this.compareTree(objA, objB, "root", this.element);
	},
    compareTree: function(a, b, name, results){
        var typeA = this.typeofReal(a);
        var typeB = this.typeofReal(b);
        
        var typeSpanA = document.createElement("span");
        typeSpanA.appendChild(document.createTextNode("(" + typeA + ")"))
        typeSpanA.setAttribute("class", "typeName");
        
        var typeSpanB = document.createElement("span");
        typeSpanB.appendChild(document.createTextNode("(" + typeB + ")"))
        typeSpanB.setAttribute("class", "typeName");
        
        var aString = (typeA == "object" || typeA == "array") ? "" : String(a) + " ";
        var bString = (typeB == "object" || typeB == "array") ? "" : String(b) + " ";
        
        var leafNode = document.createElement("span");
        leafNode.appendChild(document.createTextNode(name));
        if (a == undefined) {
            leafNode.setAttribute("class", "added");
            leafNode.appendChild(document.createTextNode(": " + bString));
            leafNode.appendChild(typeSpanB);
        }
        else 
            if (b == undefined) {
                leafNode.setAttribute("class", "removed");
                leafNode.appendChild(document.createTextNode(": " + aString));
                leafNode.appendChild(typeSpanA);
            }
            else 
                if (typeA != typeB || (typeA != "object" && typeA != "array" && a != b)) {
                    leafNode.setAttribute("class", "changed");
                    leafNode.appendChild(document.createTextNode(": " + aString));
                    leafNode.appendChild(typeSpanA);
                    leafNode.appendChild(document.createTextNode(" => " + bString));
                    leafNode.appendChild(typeSpanB);
                }
                else {
                    leafNode.appendChild(document.createTextNode(": " + aString));
                    leafNode.appendChild(typeSpanA);
                }
        
        if (typeA == "object" || typeA == "array" || typeB == "object" || typeB == "array") {
            var keys = [];
            for (var i in a) {
				(a.hasOwnProperty(i)) ? keys.push(i):null;
			}
            for (var i in b) {
				(b.hasOwnProperty(i)) ? keys.push(i):null;
			}
            keys.sort();
            
            var listNode = document.createElement("ul");
            listNode.appendChild(leafNode);
            
            for (var i = 0; i < keys.length; i++) {
                if (keys[i] == keys[i - 1]) 
                    continue;
                
                var li = document.createElement("li");
                listNode.appendChild(li);
                
                this.compareTree(a && a[keys[i]], b && b[keys[i]], keys[i], li);
            }
            
            results.appendChild(listNode);
        }
        else {
            results.appendChild(leafNode);
        }
    },
	isArray : function (value) { return value && typeof value == "object" && value instanceof Array; },
	typeofReal :function (value) { return this.isArray(value) ? "array" : typeof value; }
});
